// Dispacher.cs
//
//  Copyright (C) 2009 Jacek Trubłajewicz
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
//

using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using System.Text;
using Mono.Unix;
using HAKGERSoft;
using NLog;

namespace wxGadu
{
	/// <summary>
	/// Delegate used for async invoking DisconnectFromNetworkAsync method
	/// when user set his current status to not available
	/// </summary>
	public delegate void UserDisconnecting();
	/// <summary>
	/// Delegate user for asyc invoking ChangeStatusAsync method when
	/// set his current status to something elese than not available
	/// </summary>
	public delegate void UserChangingStatus(int newStatus);
	
	/// <summary>
	/// This class manages most of application logic
	/// </summary>
	public class Dispatcher
	{
		// this byte indicates end of incoming message
		private const byte EOM = 0;
		#region data members
		/// <summary>
		/// Main application frame, this one holds contact list in ListCtrl wigdet
		/// </summary>
		private MainFrame mainFrame;
		private sHGG gadu;
		private ContactManager contactManager;
		private DescriptionManager descManager;
		private HistoryManager historyManager;
		private ContactListProxy contactProxy;
		/// <summary>
		/// Chats that have associated chat window
		/// </summary>
		private Dictionary<int, Chat> activeChats;
		/// <summary>
		/// Chats that waiting for user acceptation 
		/// and don't have associated chat window
		/// </summary>
		private Dictionary<int, Chat> incomingChats;
		/// <summary>
		/// Chats that have been recently closed
		/// </summary>
		private LimitedDictionary<int, Chat> dismissedChats;
		private Queue<int> chatNotifyUins;
		#endregion
		private Mutex contactStatusMutex;
		private Mutex gaduUsersMutex;
		private Mutex chatListsMutex;
		private Mutex chatNotifyUinsMutex;
#if LOG
		private Logger logger = LogManager.GetCurrentClassLogger();
#endif
		
		
		public Dispatcher(wx.Frame mainFrame)
		{
			this.mainFrame = (MainFrame) mainFrame;
			this.mainFrame.UserStatusChange += new UserStatusChangeEventHandler(StatusChangeRequest);
			
			// initialize chat lists
			activeChats = new Dictionary<int, Chat>();
			incomingChats = new Dictionary<int, Chat>();
			dismissedChats = new LimitedDictionary<int, Chat>(10);
			chatNotifyUins = new Queue<int>();

			gadu = new sHGG();
			gadu.GGLogged += new sHGG.GenericEventHandler<EventArgs>(OnGGLogged);
			gadu.GGLogFailed += new sHGG.GenericEventHandler<EventArgs>(OnGGLogFailed);
			gadu.GGMessageReceive += new sHGG.GenericEventHandler<sHGG.MessageReceiveEventArgs>(OnReceiveMessage);
			gadu.Users.UserChanged += new sHGG.GenericEventHandler<GGUsers.UserEventArgs>(OnContactStatusChanged);
			gadu.GGDisconnected += new sHGG.GenericEventHandler<EventArgs>(OnGGDisconnected);
			
			
			contactManager = new ContactManager(this.mainFrame.AppCfg);
			//contactManager.contactsChanged += new ContactListChangedEventHandler(UpdateContactTrackingList);
			contactManager.ReadContactsFromFile();

			// if description should be saved initialize description manager
			if (this.mainFrame.AppCfg.UserConf.SaveDescription)
			{
				descManager = new DescriptionManager(this.mainFrame, gadu);
				descManager.FillDescriptionCombo();
			}
			
			historyManager = new HistoryManager(this);
			
			//this.mainFrame.LoadContactList(contactManager.Contacts);
			contactProxy = new ContactListProxy(gadu.Users, contactManager.Contacts, this.mainFrame);
			//gadu.Users.UserAdded += new  sHGG.GenericEventHandler<GGUsers.UserEventArgs>(UserAdded);
			contactStatusMutex = new Mutex();
			gaduUsersMutex = new Mutex();
			chatListsMutex = new Mutex();
			chatNotifyUinsMutex = new Mutex();
			
			DisplayOfflineContactList();			
		}

		public void StatusChangeRequest(int newStatus)
		{
			if (newStatus == StatusConstans.offline ||
			    newStatus == StatusConstans.offlineDescr)
			{
				UserDisconnecting disconnect = new UserDisconnecting(DisconnectFromNetworkAsync);
				disconnect.BeginInvoke(null, null);
			}
			else
			{
				UserChangingStatus changeStatus = new UserChangingStatus(ChangeStatusAsync);
				changeStatus.BeginInvoke(newStatus, null, null);
			}
		}

		/// <summary>
		/// Async invoked method used to change user satus to 
		/// offline (disconnect from network)
		/// </summary>
		private void DisconnectFromNetworkAsync()
		{
			if (gadu.IsGGLogged == true) 
			{
				
				// clears contact tracking list
				gaduUsersMutex.WaitOne();
				{
					gadu.Users.Clear();
				}
				gaduUsersMutex.ReleaseMutex();
				// change status in this order to preserve user description
				gadu.GGStatus = GGStatusType.Invisible;
				System.Threading.Thread.Sleep(200);
				gadu.GGStatus = GGStatusType.NotAvailable;
				// we set bitmap directly from this thread which cause UI lockup...
				//mainFrame.BottomWindow.StatusBitmap.Bitmap = mainFrame.StatusBitmaps[StatusConstans.offline];
				// and next we push request to update ui with special fake ID value
				// add request for setting status bitmap in main window
				mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.statusBitmap));
				// add request for setting status bitmap in tray icon
				mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.setTrayIconBitmap));
				// and finally change appearence of all contacts in ContactListCtrl
				DisplayOfflineContactList();
			}
		}

		private void ChangeStatusAsync(int newStatus)
		{
#if LOG
			logger.Trace(Catalog.GetString("Status change request"));
#endif
			//if (connected != true)
			if (gadu.IsGGLogged == false)
			{
				if (mainFrame.AppCfg.UserConf.Uin == string.Empty || mainFrame.AppCfg.Password == string.Empty)
				{
#if LOG
					logger.Warn(Catalog.GetString("Can't connect to network before setting valid GG number and password"));
#endif
					mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.emptyGaduSettingsMessageDialog));
					return;
				}
					
				gadu.GGNumber = mainFrame.AppCfg.UserConf.Uin;
				gadu.GGPassword = mainFrame.AppCfg.Password;
				try
				{
					if (mainFrame.AppCfg.UserConf.AutomaticGaduServer)
						gadu.GGLogin(gadu.GGGetActiveServer());
					else
					{
						if (mainFrame.AppCfg.UserConf.GaduServerAddress != string.Empty)
							gadu.GGLogin(mainFrame.AppCfg.UserConf.GaduServerAddress);
						else
						{
#if LOG
							logger.Warn(Catalog.GetString("Automatic GG server selecting is not active, but no valid GG server address provided. Application will select server automaticaly"));
#endif
							gadu.GGLogin(gadu.GGGetActiveServer());
						}
					}
				}
				catch(Exception e)
				{
					//TODO: check if Gadu.GGLogin() throws any exception
#if LOG
					logger.Error(Catalog.GetString("Failed to login to GG server"));
#endif
					return;
				}
			}

			// wait to get connected or hit connection timeout
			int i = 0;
			while (gadu.IsGGLogged == false && i <= mainFrame.AppCfg.UserConf.ConnectionTimeout)
			{
				i += 100;
				System.Threading.Thread.Sleep(100);
			}

			// if timeout is reached with still no connection
			// FIXME: this need improvment
			if (gadu.IsGGLogged == false)
			{
#if LOG
				logger.Error(Catalog.GetString("Connection to GG server timeout. Try using different server or/and increasing connection timeout limit"));
#endif
				mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.connectionTimeoutMessageDialog));
				gadu.GGLogout();
				return;
			}

			//set proper image for StatusBitmap in MainFrame
			switch (newStatus)
			{
				case StatusConstans.online:
					gadu.GGStatus = GGStatusType.Available;
				break;
				
				case StatusConstans.away:
					gadu.GGStatus = GGStatusType.Busy;
				 break;
				
				case StatusConstans.invisible:
					gadu.GGStatus = GGStatusType.Invisible;
				 break;
			}
			// when we set bitmap directly from child thread it will cause UI lockup...
			// mainFrame.BottomWindow.StatusBitmap.Bitmap = mainFrame.StatusBitmaps[newStatus];
			// mainFrame.StatusBitmapIcon = mainFrame.StatusBitmaps[newStatus];
			// and next we push request to update ui with special fake ID value
			// add request for setting status bitmap in main window
			mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.statusBitmap));
			// add request for setting status bitmap in tray icon
			mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.setTrayIconBitmap));
			
		}
		
		private void OnGGLogged(object sender, EventArgs evt)
		{
#if LOG
			logger.Trace(Catalog.GetString("Login to GG network successful!"));
#endif
			//fill in notify list
			UpdateNotifyList(this, contactManager.Contacts);
		}

		private void OnGGLogFailed(object sender, EventArgs evt)
		{
#if LOG
			logger.Error(Catalog.GetString("Login to GG network failed!"));
#endif
		}

		private void OnGGDisconnected(object sender, EventArgs evt)
		{
#if LOG
			logger.Warn(Catalog.GetString("Disconnected form GG network!"));
#endif
		}

		/// <summary>
		/// Route received messages to proper lists: <c>acitveChats</c> or
		/// <c>incomingChats</c>.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="System.Object"/>
		/// </param>
		/// <param name="msg">
		/// A <see cref="sHGG.MessageReceiveEventArgs"/>
		/// messsage object
		/// </param>
		/// <remarks>
		/// This message should be invoked only by 
		/// <c>Gadu.GGMessageReceive</c> event.
		/// </remarks>
		private void OnReceiveMessage(object sender, sHGG.MessageReceiveEventArgs msg)
		{
			chatListsMutex.WaitOne();
			{
#if LOG
				logger.Trace(String.Format(Catalog.GetString("Reveived new message from UIN: {0}"), msg.Number));
#endif
				string message = PreformatIncomingMessage(msg.Message);
								
				// first check if chat with this contact/uin is on active chats list
				if (activeChats.ContainsKey(msg.Number))
				{
					// route this msg to proper Chat
					activeChats[msg.Number].AddIncomingMessage(msg.Time, message);
				}
				else
				{
					// if chat is in dismissed (recently closed) chats list
					if (dismissedChats.ContainsKey(msg.Number))
					{
#if LOG
						logger.Trace(String.Format(Catalog.GetString("Restoring chat with UIN: {0} form dismissed chats list"), msg.Number));					
#endif
						// add this chat to incoming list
						incomingChats.Add(msg.Number, dismissedChats[msg.Number]);
						// remove chat form dismissed list
						dismissedChats.Remove(msg.Number);
						// route msg to its Chat
						incomingChats[msg.Number].AddIncomingMessage(msg.Time, message);
						// now set message symbol as tray icon
						mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.setTrayIconBitmap));
						// show notify window if user want to
						if (mainFrame.AppCfg.UserConf.ShowNewChatNotify)
						{
							chatNotifyUinsMutex.WaitOne();
							{
								chatNotifyUins.Enqueue(msg.Number);
							}
							chatNotifyUinsMutex.ReleaseMutex();
							mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.showNewChatNotify));
						}
						// TODO: set message icon if this UIN is on contact list
					}
					else // if chat isn't in dismissed (recently closed) chats list
					{
						// if chat with this user was not accepted yet and waits in incoming list
						if (incomingChats.ContainsKey(msg.Number))
						{
							// route msg to its Chat
							incomingChats[msg.Number].AddIncomingMessage(msg.Time, message);
						}
						else	// this is the first message so create new chat
						{
							// create new chat
							Chat newChat = new Chat(msg.Number, this);
							// add chat to incoming list
							incomingChats.Add(msg.Number, newChat);
							// route msg to its Chat
							incomingChats[msg.Number].AddIncomingMessage(msg.Time, message);
							// now set message symbol as tray icon
							mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.setTrayIconBitmap));
							// show notify window if user want to
							if (mainFrame.AppCfg.UserConf.ShowNewChatNotify)
							{
								chatNotifyUinsMutex.WaitOne();
								{
									chatNotifyUins.Enqueue(msg.Number);
								}
								chatNotifyUinsMutex.ReleaseMutex();
								mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.showNewChatNotify));
							}
							// TODO: set message icon if this UIN is on contact list
						}
					}
				}
			}
		 	chatListsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Activates any chat that waits on <c>incomingChats</c> list
		/// </summary>
		/// <remarks>
		/// Chat window is created and opened automaticaly, normal (status indicating) tray 
		/// icon is restored.
		/// </remarks>
		public void ActivateIncomingChats()
		{
			chatListsMutex.WaitOne();
			{
				// look through incoming chats list and create chat windows
				foreach (int key in incomingChats.Keys)
				{
					activeChats.Add(key, incomingChats[key]);
					string title = activeChats[key].CallerName == string.Empty ? activeChats[key].CallerUin.ToString() : activeChats[key].CallerName; 
					title += Catalog.GetString(": Chat wxGadu");
					activeChats[key].ChatWindow = new ChatFrame(mainFrame, wx.Window.wxID_ANY,
					                                            title, 
					                                            wx.Window.wxDefaultPosition, wx.Window.wxDefaultSize,
					                                            wx.Window.wxDEFAULT_FRAME,
					                                            activeChats[key]);
					wx.Icon windowIcon = new wx.Icon();
					windowIcon.CopyFromBitmap(mainFrame.ImageMgr[ImageManager.NEW_CHAT]);
					activeChats[key].ChatWindow.Icon = windowIcon;
					activeChats[key].ChatWindow.Position = mainFrame.AppCfg.UserConf.ChatWindowPosition;
				}
				incomingChats.Clear();
				
				// display chat windows
				foreach(Chat chat in activeChats.Values)
					chat.ChatWindow.Show(true);

				// set normal icon in tray
				mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.setTrayIconBitmap));
			}
			chatListsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Creates new chat (creates new, activates from <c>incomingChats</c> list or 
		/// restores from <c>dismissedChats</c> list) after user request (i.e. double click on
		/// <c>ContactListCtrl</c>)
		/// </summary>
		/// <param name="contactUin">
		/// A <see cref="System.Int32"/>
		/// UIN of contact with whom chat should be initiated
		/// </param>
		public void CreateNewChat(int contactUin)
		{
			chatListsMutex.WaitOne();
			{
				// check if we have connection to GG network
				if (gadu.IsGGLogged)
				{				
					//check if chat with this contact is on incoming list
					if (incomingChats.ContainsKey(contactUin))
					{
						//TODO: activate chat only with choosen UIN
						ActivateIncomingChats();
					}
					else
					{
						// if chat is on active chats list
						if (activeChats.ContainsKey(contactUin))
						{
							// show chat window
							if (activeChats[contactUin].ChatWindow.Iconized)
								activeChats[contactUin].ChatWindow.Iconized = false;
							
							activeChats[contactUin].ChatWindow.SetFocus();
						}
						else	// create new chat with choosen contact
						{
							if (dismissedChats.ContainsKey(contactUin))
							{
								// move chat from dismissed to acitve chats
								activeChats.Add(contactUin, dismissedChats[contactUin]);
								dismissedChats.Remove(contactUin);
							}
							else
							{
								activeChats.Add(contactUin, new Chat(contactUin, this));
							}
							string title = activeChats[contactUin].CallerName == string.Empty ? activeChats[contactUin].CallerUin.ToString() : activeChats[contactUin].CallerName; 
							title += Catalog.GetString(": Chat wxGadu");
							
							activeChats[contactUin].ChatWindow = new ChatFrame(mainFrame, wx.Window.wxID_ANY,
							                                            title, 
							                                            wx.Window.wxDefaultPosition, wx.Window.wxDefaultSize,
							                                            wx.Window.wxDEFAULT_FRAME,
							                                            activeChats[contactUin]);
							
							wx.Icon windowIcon = new wx.Icon();
							windowIcon.CopyFromBitmap(mainFrame.ImageMgr[ImageManager.NEW_CHAT]);
							activeChats[contactUin].ChatWindow.Icon = windowIcon;
							activeChats[contactUin].ChatWindow.Position = mainFrame.AppCfg.UserConf.ChatWindowPosition;
							
							activeChats[contactUin].ChatWindow.Show(true);
						}
					}
				}
			}
			chatListsMutex.ReleaseMutex();
		}
		
		/// <summary>
		/// Sends chat message to specified recipient
		/// </summary>
		/// <param name="recipientUin">
		/// A <see cref="System.Int32"/>
		/// UIN of recipient
		/// </param>
		/// <param name="message">
		/// A <see cref="System.String"/>
		/// message to be sent
		/// </param>
		public void SendMessage(int recipientUin, string message)
		{
//			string messageCp1250 = ;
			try
			{
				gadu.GGSendMessage(recipientUin, message);
			}
			catch (sHGGException e)
			{
#if LOG
				logger.ErrorException(Catalog.GetString("Attempt to send message with no connection to GG network"), e);
#endif
				chatListsMutex.WaitOne();
				{
					activeChats[recipientUin].ChatWindow.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.showNotConnectedDialog));
				}
				chatListsMutex.ReleaseMutex();
				return;
			}
#if LOG
			logger.Trace(String.Format(Catalog.GetString("Message to UIN: {0} sending successful"), recipientUin));
#endif
			// remove html tags before proper formatting and rendering
			string formattedMessage = PreformatOutgoingMessage(message);
			
			// add message to list
			chatListsMutex.WaitOne();
			{
				activeChats[recipientUin].AddOutgoingMessage(DateTime.Now, formattedMessage);
			}
			chatListsMutex.ReleaseMutex();
		}
		
		public void CloseChat(int callerUin)
		{
			chatListsMutex.WaitOne();
			{
#if LOG
				logger.Trace(String.Format(Catalog.GetString("Closing chat with UIN: {0}"), callerUin));
#endif
				
				// TODO: save history if user want to
				historyManager.DumpChatToFile(callerUin);

				if (activeChats.ContainsKey(callerUin))
				{
#if LOG
					logger.Trace(String.Format(Catalog.GetString("Moving chat with UIN: {0} to dismissed list"), callerUin));
#endif
					dismissedChats.Add(callerUin, activeChats[callerUin]);
					// setting chat window tu null
					dismissedChats[callerUin].ChatWindow = null;
					activeChats.Remove(callerUin);
				}
				else
				{
#if LOG
					logger.Error(String.Format(Catalog.GetString("Active chat list doesn't contain UIN: {0}"), callerUin));
#endif
					throw new ApplicationException(String.Format(Catalog.GetString("Active chat list doesn't contain UIN: {0}"), callerUin));
				}
			}
			chatListsMutex.ReleaseMutex();
		}
		
		
		/// <summary>
		/// Do initial incoming message formatting
		/// </summary>
		/// <param name="message">
		/// A <see cref="System.String"/>
		/// message to be preformatted
		/// </param>
		/// <returns>
		/// A <see cref="System.String"/>
		/// preformatted message
		/// </returns>
		private string PreformatIncomingMessage(string message)
		{
			// remove any trash at the end of the message
			string newMsg = message.Remove(message.IndexOf((char) EOM));
			
			// convert any html tags into safe strings ;)
			newMsg = Regex.Replace(newMsg, "<", "&lt;");
			newMsg = Regex.Replace(newMsg, ">", "&gt;");
			
			// convert new line symbols into html tags
			newMsg = newMsg.Replace(Environment.NewLine, "<br />");
			
			return newMsg;
		}
		
		/// <summary>
		/// Do initial outgoing message formatting
		/// </summary>
		/// <param name="message">
		/// A <see cref="System.String"/>
		/// message to be preformatted
		/// </param>
		/// <returns>
		/// A <see cref="System.String"/>
		/// preformatted message
		/// </returns>
		private string PreformatOutgoingMessage(string message)
		{
			string newMsg = message;
			
			// convert any html tags into safe strings ;)
			newMsg = Regex.Replace(newMsg, "<", "&lt;");
			newMsg = Regex.Replace(newMsg, ">", "&gt;");
			
			// convert new line symbols into html tags
			newMsg = newMsg.Replace(Environment.NewLine, "<br />");
			
			return newMsg;
		}
		
	
		/// <summary>
		/// Invoked when contact from contact list change status.
		/// Connected to gadu.Users.UserChanged event.
		/// </summary>
		/// <param name="sender">
		/// A <see cref="System.Object"/>
		/// </param>
		/// <param name="contact">
		/// A <see cref="HAKGERSoft.GGUsers.UserEventArgs"/>
		/// </param>
		private void OnContactStatusChanged(object sender, HAKGERSoft.GGUsers.UserEventArgs contact)
		{
			contactStatusMutex.WaitOne();
			{
				// inform mainFrame that ContactListCtrl need to be updated
				GGUser clonedUser = (GGUser) contact.User.Clone();
				mainFrame.OutdatedUsers.Enqueue(clonedUser);				
				mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.contactListCtrl));
				
				// show notify window if user want to
				if (mainFrame.AppCfg.UserConf.EnableContactStatusNotify)
				{
					if (mainFrame.AppCfg.UserConf.TrackedContacts.Contains(clonedUser.GGNumber))
					{
						if (mainFrame.AppCfg.UserConf.ShowOnlyAvailableNotify)
						{
							if (clonedUser.GGStatus == GGStatusType.Available || clonedUser.GGStatus == GGStatusType.Busy)
							{
#if LOG
								logger.Trace(String.Format(Catalog.GetString("Showing status notification window for UIN: {0}"), clonedUser.GGNumber));
#endif
								mainFrame.StatusChangNotifyMutex.WaitOne();
								{
									ContactStatusChangeData cdata = new ContactStatusChangeData();
									cdata.uin = clonedUser.GGNumber;
									cdata.status = StatusConstans.StatusToString(StatusConstans.FromGGStatus(clonedUser.GGStatus));
									mainFrame.StatusChangeNotifyData = cdata;
									mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.showStatusChangeNotify));
								}
								mainFrame.StatusChangNotifyMutex.ReleaseMutex();
							}
						}
						else
						{
#if LOG
							logger.Trace(String.Format(Catalog.GetString("Showing status notification window for UIN: {0}"), clonedUser.GGNumber));
#endif
							mainFrame.StatusChangNotifyMutex.WaitOne();
							{
								ContactStatusChangeData cdata = new ContactStatusChangeData();
								cdata.uin = clonedUser.GGNumber;
								cdata.status = StatusConstans.StatusToString(StatusConstans.FromGGStatus(clonedUser.GGStatus));
								mainFrame.StatusChangeNotifyData = cdata;
								mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.showStatusChangeNotify));
							}
							mainFrame.StatusChangNotifyMutex.ReleaseMutex();
						}
					}
				}
			}
			contactStatusMutex.ReleaseMutex();
		}
			
		public void Destroy()
		{
			DisconnectFromNetworkAsync();	
			
			// save descriptions list if user want to
			if (descManager is DescriptionManager)
			{
				descManager.SaveDescriptions();
			}
		}

		/// <summary>
		/// Fills gadu.Users and ContactListProxy with contacts
		/// on ContactManager.Contacts
		/// </summary>
		/// <param name="sender">
		/// A <see cref="System.Object"/>
		/// </param>
		/// <param name="contacts">
		/// A <see cref="Dictionary"/>
		/// </param>
		private void UpdateNotifyList(object sender, Dictionary<int, ContactInfo> contacts)
		{
			// remove everything on ContactListProxy
			mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.contactListClear));	
			Thread.Sleep(50);
			
			gaduUsersMutex.WaitOne();
			{
				// remove everything on the tracking list
				gadu.Users.Clear();
				
				//now we add all contacts to status tracking list
				int i = 0;
				foreach (int uin in contacts.Keys)
				{
					if (!gadu.Users.Contains(uin)) 
					{
						GGUser newUser = new GGUser();
						newUser.GGNumber = uin;
						newUser.GGNick = contacts[uin].Name;
						
						// first add user to ContactListProxy positions list
						GGUser clonedUser = (GGUser) newUser.Clone();
						mainFrame.AddedUsers.Enqueue(clonedUser);				
						mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.contactAdded));
						
						// then add to gadu list
						gadu.Users.Add(newUser);
					}
				}
				gaduUsersMutex.ReleaseMutex();
			}
			//mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.contactAdded));
			//next we must store contact position in list so we
			//will know on which position specifiic contact is in the ContactListCtrl
			contactManager.SyncPositions(gadu.Users);		
		}
		
		/// <summary>
		/// Displays contact list in offline mode, 
		/// sorted alphabeticaly by name.
		/// </summary>
		/// <remarks>
		/// This method is thread safe and can be called form other threads.
		/// </remarks>
		private void DisplayOfflineContactList()
		{
			// display this list only when we are connected
			if (gadu.IsGGLogged)
			{
#if LOG 
				logger.Warn("Trying to display offline contact list when connected to GG network");
#endif
				return;
			}
			
			// remove everything on ContactListProxy
			mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.contactListClear));	
			Thread.Sleep(50);
			
			List<ContactInfo> offlineList = new List<ContactInfo>();
			foreach(ContactInfo ci in contactManager.Contacts.Values)
			{
				ci.Status = StatusConstans.offline;
				offlineList.Add(ci);
			}
			offlineList.Sort();

			for (int i = 0; i < offlineList.Count; i++)
				contactManager.Contacts[offlineList[i].Uin].Position = i;
				
			foreach (ContactInfo ci in offlineList)
			{
				GGUser newUser = new GGUser();
				newUser.GGNumber = ci.Uin;
				
				mainFrame.AddedUsers.Enqueue(newUser);
			}
			mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.contactAdded));
		}
		
		/// <summary>
		/// Updates controls and data structures when contact UIN 
		/// has been changed
		/// </summary>
		/// <param name="newContactData">
		/// A <see cref="ContactInfo"/>
		/// Object containig new contact information
		/// </param>
		/// <param name="oldUin">
		/// A <see cref="System.Int32"/>
		/// Old (the previous one) contact's UIN
		/// <remarks><c>oldUin</c>can be the same
		/// as <c>newContactData.Uin</c> if only name has changed
		/// </remarks>
		/// </param>
		public void ContactUinNameChanged(ContactInfo newContactData, int oldUin)
		{
			// copy status and Gadu.Users position
			newContactData.Position = ContactMgr.Contacts[oldUin].Position;
			newContactData.Status = ContactMgr.Contacts[oldUin].Status;
			
			// if contact is on status tracking list change its uin
			if (mainFrame.AppCfg.UserConf.TrackedContacts.Contains(oldUin))
			{
				mainFrame.AppCfg.UserConf.TrackedContacts.Remove(oldUin);
				mainFrame.AppCfg.UserConf.TrackedContacts.Add(newContactData.Uin);
			}
			
			if (newContactData.Uin != oldUin)
			{	// contact uin has changed
				// update contacts file
				ContactMgr.UpdateContactInFile(newContactData, oldUin);
				// update ContactManager.Contacts
				ContactMgr.Contacts.Remove(oldUin);
				ContactMgr.Contacts.Add(newContactData.Uin, newContactData);
			}
			else
			{	// only name has changed
				// update ContactManager.Contacts
				ContactMgr.Contacts.Remove(oldUin);
				ContactMgr.Contacts.Add(newContactData.Uin, newContactData);
				// update contacts file
				ContactMgr.UpdateContactInFile(newContactData.Uin);
			}
			
			if (gadu.IsGGLogged)
			{
				gaduUsersMutex.WaitOne();
				{
					// remove old contact form Gadu.Users
					gadu.Users.Remove(oldUin);
					// remove old from ContactListProxy and ContactListCtrl
					//contactProxy.RemoveContact(oldUin);
					contactProxy.DeleteContact(oldUin);
					// create new contact and add it to ContactListProxy and Gadu.Users
					GGUser newUser = new GGUser();
					newUser.GGNumber = newContactData.Uin;
					newUser.Name = newContactData.Name;
					contactProxy.AddContact(newUser);
					gadu.Users.Add(newUser);
				}
				gaduUsersMutex.ReleaseMutex();
			}
			else
			{
				DisplayOfflineContactList();
			}
			
			// if contact info is displayed on BottomPanel update it
			if (mainFrame.BottomWindow.VisibleUin == oldUin)
				mainFrame.BottomWindow.DisplayContactInfo(newContactData.Uin);
			
		}

// 		[ObsoleteAttribute]	
//		public void SetUserDescription(string description)
//		{
//			// FIXME: description need to encode in windows 1250, seems to be lack in SHGG
//			// TODO: seting description should be...
//			System.Text.Encoding cp1250 = System.Text.Encoding.GetEncoding(1250);
//			System.Text.StringBuilder sb = new System.Text.StringBuilder();
//			byte[] descb = cp1250.GetBytes(description);
//			gadu.GGDescription = cp1250.GetString(descb);
//		}
		
		/// <summary>
		/// Invoked when user change <c>UserConfig</c> settings.
		/// </summary>
		/// <remarks>
		/// If user select not to save descriptions list, <c>DescriptionManager</c>
		/// object is destroyed and descriptions file is deleted if exists.
		/// Also description combo box is purged and only current description
		/// stays active.
		/// If user select to save descriptions list, <c>DescriptionManager</c>
		/// object is created and description combo box is filled with loaded (from file)
		/// descriptions. 
		/// </remarks>
		/// <param name="sender">
		/// A <see cref="System.Object"/>
		/// </param>
		public void DescriptionSettingsChanged(object sender)
		{
			if (mainFrame.AppCfg.UserConf.SaveDescription == false)
			{
				if (descManager is DescriptionManager)
				{
					descManager = null;
					// delete descriptions file if user want to
					mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.deleteDescriptionsFileDialog));
					// clear description combo box
					mainFrame.EventHandler.AddPendingEvent(new wx.UpdateUIEvent((int) FakeId.clearDescriptionCombo));
				}
		    }
			else
			{
				if (!(descManager is DescriptionManager))
				{
					descManager = new DescriptionManager(mainFrame, gadu);
					if (mainFrame.BottomWindow.UserDescriptionCombo.Value.Trim() != string.Empty)
						descManager.AddDescription(mainFrame.BottomWindow.UserDescriptionCombo.Value.Trim());
					descManager.FillDescriptionCombo();
				}
			}
	    }
	
		/// <summary>
		/// Sets up new logging rules. 
		/// </summary>
		/// <remarks>
		/// Invoked when user change <c>UserConfig</c> settings.
		/// </remarks>
		/// <param name="sender">
		/// A <see cref="System.Object"/>
		/// </param>
		public void LoggingSettingsChanged(object sender)
		{
#if LOG
			logger.Trace(Catalog.GetString("Setting new logging rules"));
#endif
			NLog.Config.LoggingConfiguration configuration = LogManager.Configuration;
			
			NLog.Targets.ConsoleTarget console = (NLog.Targets.ConsoleTarget) configuration.FindTargetByName(AppConfig.CONSOLE_LOG_TARGET_NAME);
			NLog.Targets.FileTarget file = (NLog.Targets.FileTarget) configuration.FindTargetByName(AppConfig.FILE_LOG_TARGET_NAME);
			
			configuration.LoggingRules.Clear();
			
			NLog.Config.LoggingRule consoleRule = new NLog.Config.LoggingRule("*", NLog.LogLevel.FromString(mainFrame.AppCfg.UserConf.ConsoleLoggingLevel.ToString()), console); 
			configuration.LoggingRules.Add(consoleRule); 
			NLog.Config.LoggingRule fileRule = new NLog.Config.LoggingRule("*", NLog.LogLevel.FromString(mainFrame.AppCfg.UserConf.FileLoggingLevel.ToString()), file); 
			configuration.LoggingRules.Add(fileRule);
			
			LogManager.Configuration = configuration;
		}
		
		//// <value>
		/// Network library for Gadu-Gadu support object
		/// </value>
		public sHGG Gadu
		{
			get { return gadu; }
		}
		
		//// <value>
		/// Main application window
		/// </value>
		public MainFrame MainWindow
		{
			get { return mainFrame; }
		}
		
		public Mutex ContactStatusMutex
		{
			get { return contactStatusMutex; }
		}
		
		public Mutex GaduUsersMutex
		{
			get { return gaduUsersMutex; }
		}
		
		public Mutex ChatListMutex
		{
			get { return chatListsMutex; }
		}
		
		public Mutex ChatNotifyUinsMutex
		{
			get { return chatNotifyUinsMutex; }
		}
		
		//// <value>
		/// Contacts manager
		/// </value>
		public ContactManager ContactMgr
		{
			get { return contactManager; }
		}
		
		//// <value>
		/// History manager
		/// </value>
		public HistoryManager HistoryMgr
		{
			get { return historyManager; }
		}
		
		//// <value>
		///  Proxy between <c>Gadu.Users</c> and
		/// <c>ContactListCtrl</c>. Keeps information which
		/// contact is on which position in widget list
		/// </value>
		public ContactListProxy ContactProxy
		{
			get { return contactProxy; }
		}
		
		//// <value>
		/// Description manager
		/// </value>
		public DescriptionManager DescriptionMgr
		{
			get { return descManager; }
		}
		
		//// <value>
		/// Application config.
		/// Keeps basic application configuration data.
		/// </value>
		public AppConfig AppCfg
		{
			get { return mainFrame.AppCfg; }
		}
					
		//// <value>
		/// List of a Chats that waiting for user acceptation 
		/// and don't have associated chat window 
		/// </value>
		public Dictionary<int, Chat> IncomingChats
		{
			get { return incomingChats; }
		}
		
		//// <value>
		/// Listo of Chats that have associated chat window
		/// </value>
		public Dictionary<int, Chat> ActiveChats
		{
			get { return activeChats; }
		}
		
		//// <value>
		/// List of contacts for which new chat notify 
		/// window should be displayed
		/// </value>
		public Queue<int> ChatNotifyUins
		{
			get { return chatNotifyUins; }
		}
		
//		static string Convert( string inputString)
//		{
//			
//		byte[] znaki = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(1250), Encoding.UTF8.GetBytes(inputString));
//		char[] znakic = Encoding.GetEncoding(1250).GetChars(znaki);
////			char [] znakic = .
//		System.Console.WriteLine("znaki");
//		for (int i = 0; i < znaki.Length; i++)
//		{
//			System.Console.Write("{0} ", znaki[i]);
//		}
//		System.Console.WriteLine("\nnakic");
//		for (int i = 0; i < znakic.Length; i++)
//		{
//			System.Console.Write("{0} ", (int) znakic[i]);
//		}
////			System.Console.WriteLine("\n1252:");
////			string conb = Encoding.GetEncoding(1250).GetString(znaki);
////			
////			for (int i = 0; i < conb.Length; i++)
////			{
////				System.Console.Write("{0} ", (int) conb[i]);
////			}
//
//		
////System.Console.WriteLine("po konwersji: " + conb);
////			FileStream fs = new FileStream("znak.txt", FileMode.Create, FileAccess.Write);
////			StreamWriter sw = new StreamWriter(fs);
////			sw.Write(conb);
////			fs.Close();
//
//			return  "ggg";
//		}
	}
}
